<?php

// +----------------------------------------------------------------------
// | OneThink [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2014 http://www.topthink.com All rights reserved.
// +----------------------------------------------------------------------
// | Author: 云无心 <tzzhangyajun@vip.qq.com>
// +----------------------------------------------------------------------

namespace Kcdns\Service\Pay\SDK\Driver;


use Kcdns\Service\Pay\SDK\Pay;

class Aliwap extends Pay {

    protected $gateway    = 'https://openapi.alipay.com/gateway.do';

    protected $config     = array(
        'app_id'     => '',
        'pubkey'     => '',//应用RSA公钥
        'prikey'     => '',//应用RSA私钥
        'ali_pubkey' => '',//支付宝公钥
    );

    protected $signType = 'RSA2';

    protected $postCharset = 'utf-8';

    public function check() {
        if (!$this->config['app_id'] || !$this->config['pubkey'] || !$this->config['prikey']) {
            throw new \Exception("支付宝设置有误！");
        }
        return true;
    }

    public function buildRequestForm($paymentInfo=array()) {
        if (isset($this->config['extra']['appType'])) {

            //组装系统参数
            $sysParams["app_id"] = $this->config['app_id'];
            $sysParams["method"] = "alipay.trade.app.pay";
            $sysParams["format"] = 'JSON';
            $sysParams["charset"] = 'utf-8';
            $sysParams["sign_type"] = 'RSA2';
            $sysParams["timestamp"] = date("Y-m-d H:i:s");
            $sysParams["version"] = '1.0';
            $sysParams["notify_url"] = $paymentInfo['notify'];

            $orderInfo = unserialize($paymentInfo['order_info']);
            $bizContent = array(
                'body' => $orderInfo['title'] ?: "订单支付",
                'subject' => $orderInfo['desc'] ?: "订单支付",
                'out_trade_no' => $paymentInfo['order_no'],
                'timeout_express' => '2m',
                'total_amount' => $paymentInfo['amount'],
                'product_code' => 'QUICK_MSECURITY_PAY'
            );
            $sysParams['biz_content'] = json_encode($bizContent, JSON_UNESCAPED_UNICODE);

            // 生成签名
            $sysParams['sign'] = $this->createSign($sysParams);

            foreach ($sysParams as $k => &$v) {
                $v = urlencode($v);
            }
            $sHtml = $this->_getPreSignStr($sysParams);

        } else {
            //组装系统参数
            $sysParams["app_id"] = $this->config['app_id'];
            $sysParams["version"] = '2.0';
            $sysParams["format"] = 'JSON';
            $sysParams["sign_type"] = 'RSA2';
            $sysParams["method"] = "alipay.trade.wap.pay";
            $sysParams["timestamp"] = date("Y-m-d H:i:s");
            //$sysParams["alipay_sdk"] = $this->alipaySdkVersion;
            //$sysParams["terminal_type"] = $request->getTerminalType();
            //$sysParams["terminal_info"] = $request->getTerminalInfo();
            //$sysParams["prod_code"] = $request->getProdCode();
            $sysParams["notify_url"] = $paymentInfo['notify'];
            $sysParams["return_url"] = $paymentInfo['return'];
            $sysParams["charset"] = 'utf-8';


            $orderInfo = unserialize($paymentInfo['order_info']);
            $bizContent = array(
                'body' => $orderInfo['title'] ?: "订单支付",
                'subject' => $orderInfo['desc'] ?: "订单支付",
                'out_trade_no' => $paymentInfo['order_no'],
                'timeout_express' => '2m',
                'total_amount' => $paymentInfo['amount'],
                'product_code' => 'QUICK_WAP_WAY'
            );

            $sysParams['biz_content'] = json_encode($bizContent, JSON_UNESCAPED_UNICODE);
            // 生成签名
            $sysParams['sign'] = $this->createSign($sysParams);

            //生成表单
            $sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='" . $this->gateway . "?charset=" . trim($this->postCharset) . "' method='POST'>";
            while (list ($key, $val) = each($sysParams)) {
                if (false === $this->_checkEmpty($val)) {
                    //$val = $this->characet($val, $this->postCharset);
                    $val = str_replace("'", "&apos;", $val);
                    //$val = str_replace("\"","&quot;",$val);
                    $sHtml .= "<input type='hidden' name='" . $key . "' value='" . $val . "'/>";
                }
            }

            //submit按钮控件请不要含有name属性
            $sHtml = $sHtml . "<input type='submit' value='ok' style='display:none;''></form>";

            $sHtml = $sHtml . "<script>document.forms['alipaysubmit'].submit();</script>";

        }
        return $sHtml;
    }

    //生成接口请求签名
    protected function createSign($params) {
        ksort($params);
        $sign = $this->_sign($params, $this->signType);
        return $sign;
    }

    //获取同步 异步通知数据
    public function getRequest($type = 'notify')
    {
        $iget = I('get.');
        $ipost = I('post.');

        unset($iget['paysn']);
        $response="";
        switch($type){
            //支付同步通知
            case 'return':
                if (IS_POST && !empty($ipost)) {
                    $response = $ipost;
                } elseif (IS_GET && !empty($iget)) {
                    $response = $iget;
                } else {
                    throw new \Exception('Access Denied');
                }
                break;
            //支付异步通知
            case 'notify':
                $response=$_POST;
                break;
            //退款同步通知(为本地模拟同步通知）退款暂无第三方同步通知 本地直接同步处理
            case 'rreturn':
                $response=$_GET;
                break;
            //退款异步通知 需要解密数据
            case 'rnotify':
                $response=$_POST;
                break;
            default:
                $response="";
        }

        return $response;
    }

    //通知数据验证签名
    public function verifyNotify($notifyData) {
//        if(IS_GET){
//            foreach($notifyData as $k=>$v){
//                $notifyData[$k] = urldecode($v);
//            }
//        }
        //采用支付宝提供的公钥进行签名解密验证
        $result = $this->_rsaCheckV1($notifyData, $this->config['ali_pubkey'] ,$this->signType);
        if($result){
            //签名通过 验证支付状态
            $info = array();
            if(IS_GET){
                //同步通知 订单查询
                $paymentInfo=array(
                    'order_no'=>$notifyData['out_trade_no'],
                    'outer_sn'=>$notifyData['trade_no'],
                );
                $queryInfo = $this->queryOrder($paymentInfo);
                //\KCSLog::APP('PAY_QUERY',var_export($queryInfo,true));
                if($queryInfo['trade_status']=='TRADE_FINISHED'||$queryInfo['trade_status']=='TRADE_SUCCESS'){
                    //支付成功
                    $trade_status         = $notifyData['trade_status'];
                    $info['status']       = ($trade_status == 'TRADE_FINISHED' || $trade_status == 'TRADE_SUCCESS') ? true : false;
                    $info['out_trade_no'] = $notifyData["out_trade_no"];
                    $this->info=$info;
                    return $this->info['status'];
                }
                //支付订单不存在或者订单状态异常 支付失败
                return false;
            }elseif(IS_POST){
                //异步通知 以异步通知状态为准
                $trade_status         = $notifyData['trade_status'];
                $info['status']       = ($trade_status == 'TRADE_FINISHED' || $trade_status == 'TRADE_SUCCESS') ? true : false;
                $info['out_trade_no'] = $notifyData["out_trade_no"];
            }

            $this->info = $info;
            //返回验证状态 同步直接返回通过 异步返回第三方推送状态
            return $this->info['status'];
        }
        //签名验证失败
        return false;
    }

    //获取异步通知时外部订单号
    public function getOuterNo($notifyData=array()){
        return $notifyData['trade_no'];
    }
    //退款
    public function refund($outerNo,$refundAmount,$refundRecord=array(),$paymentRecord=array(),$refundNo=''){
        //组装系统参数
        $sysParams["app_id"] = $this->config['app_id'];
        $sysParams["version"] = '2.0';
        $sysParams["format"] = 'JSON';
        $sysParams["sign_type"] = 'RSA2';
        $sysParams["method"] = "alipay.trade.refund";
        $sysParams["timestamp"] = date("Y-m-d H:i:s");
        //$sysParams["alipay_sdk"] = $this->alipaySdkVersion;
        //$sysParams["terminal_type"] = $request->getTerminalType();
        //$sysParams["terminal_info"] = $request->getTerminalInfo();
        //$sysParams["prod_code"] = $request->getProdCode();
        $sysParams["charset"] = 'utf-8';


        $bizContent=array(
            'out_trade_no'=>$paymentRecord['order_no'],
            'trade_no'=>$outerNo,
            'out_trade_no'=>$paymentRecord['order_no'],
            'refund_amount'=>$refundAmount,
            'out_request_no'=>$refundNo?:($refundRecord['order_no'].'T'.date('YmdHi')),
        );
        $sysParams['biz_content'] = json_encode($bizContent,JSON_UNESCAPED_UNICODE);
        //生成签名
        $sysParams['sign'] = $this->createSign($sysParams, $this->signType);

        $response = $this->_curl($this->gateway, $sysParams);

        $response=json_decode($response,true);

        //记录接口请求及响应
        $this->_savePrePayInfo($refundRecord['payment_id'],$sysParams,$response);

        if(!$response) throw new \Exception('退款响应获取失败');

        $apiResponse = $response['alipay_trade_refund_response'];
        if($apiResponse['code']=='10000'){
            //返回退款成功的统一格式数据
            return $this->refundSuccess($apiResponse['trade_no'],$apiResponse['trade_no'],$apiResponse['refund_fee']);
        }

        //退款失败
        throw new \Exception('退款请求失败: errCode['.$apiResponse['code'].']errMsg:'.$apiResponse['msg']);
    }

    //支付查询
    public function queryOrder($paymentInfo){
        //组装系统参数
        $sysParams["app_id"] = $this->config['app_id'];
        $sysParams["version"] = '2.0';
        $sysParams["format"] = 'JSON';
        $sysParams["sign_type"] = 'RSA2';
        $sysParams["method"] = "alipay.trade.query";
        $sysParams["timestamp"] = date("Y-m-d H:i:s");
        //$sysParams["alipay_sdk"] = $this->alipaySdkVersion;
        //$sysParams["terminal_type"] = $request->getTerminalType();
        //$sysParams["terminal_info"] = $request->getTerminalInfo();
        //$sysParams["prod_code"] = $request->getProdCode();
        $sysParams["charset"] = 'utf-8';


        $bizContent=array(
            'out_trade_no'=>$paymentInfo['order_no'],
            'trade_no'=>$paymentInfo['outer_sn'],
        );
        $sysParams['biz_content'] = json_encode($bizContent,JSON_UNESCAPED_UNICODE);
        //生成签名
        $sysParams['sign'] = $this->createSign($sysParams, $this->signType);

        $response = $this->_curl($this->gateway, $sysParams);

        $response=json_decode($response,true);


        if(!$response) throw new \Exception('查询支付订单失败');

        return $response['alipay_trade_query_response'];
    }


    //退款同步结果返回
    public function refundSuccess($outer_sn,$order_no,$refundAmoun){
        return array(
            'outer_sn'=>$outer_sn,
            'order_no'=>$order_no,
            'refund_amount'=>$refundAmoun
        );
    }

    //利用prikey进行签名计算
    protected function _sign($params, $signType = "RSA") {
        $data = $this->_getPreSignStr($params);
        if(!$this->_checkEmpty($this->config['prikey'])){
            $res = "-----BEGIN RSA PRIVATE KEY-----\n" .
                wordwrap($this->config['prikey'], 64, "\n", true) .
                "\n-----END RSA PRIVATE KEY-----";
        }else {
            throw new \Exception('您使用的私钥格式错误，请检查RSA私钥配置');
        }


        if ("RSA2" == $signType) {
            openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
        } else {
            openssl_sign($data, $sign, $res);
        }

        $sign = base64_encode($sign);
        return $sign;
    }
    //获取待签名字符串
    protected function _getPreSignStr($params) {
        ksort($params);
        $stringToBeSigned = "";
        $i = 0;
        foreach ($params as $k => $v) {
            if (false === $this->_checkEmpty($v) && "@" != substr($v, 0, 1)) {
                if ($i == 0) {
                    $stringToBeSigned .= "$k" . "=" . "$v";
                } else {
                    $stringToBeSigned .= "&" . "$k" . "=" . "$v";
                }
                $i++;
            }
        }

        unset ($k, $v);
        return $stringToBeSigned;
    }
    /**
     * 校验$value是否非空
     *  if not set ,return true;
     *    if is null , return true;
     **/
    protected function _checkEmpty($value) {
        if (!isset($value))
            return true;
        if ($value === null)
            return true;
        if (trim($value) === "")
            return true;

        return false;
    }
    //异步、同步通知RSA验证签名
    protected function _rsaCheckV1($params,$pubKey,$signType='RSA') {
        $sign = $params['sign'];
        unset($params['sign']);
        unset($params['sign_type']);
        reset($params);
        $preSignStr=$this->_getPreSignStr($params);
        //签名比对
        return $this->_verify($preSignStr, $sign, $pubKey,$signType);
    }
    //执行签名验证
    protected function _verify($data, $sign, $pubKey, $signType = 'RSA') {
        if(!$this->_checkEmpty($pubKey)){
            $res = "-----BEGIN PUBLIC KEY-----\n" .
                wordwrap($pubKey, 64, "\n", true) .
                "\n-----END PUBLIC KEY-----";
        }
        if(!$res) throw new \Exception('支付宝RSA公钥错误。请检查公钥文件格式是否正确');

        //调用openssl内置方法验签，返回bool值
        if ("RSA2" == $signType) {
            $result = (bool)openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
        } else {
            $result = (bool)openssl_verify($data, base64_decode($sign), $res);
        }
        return $result;
    }
    //curl
    protected function _curl($url,$postFields){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_FAILONERROR, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        $postBodyString = "";
        $encodeArray = Array();
        $postMultipart = false;


        if (is_array($postFields) && 0 < count($postFields)) {

            foreach ($postFields as $k => $v) {
                if ("@" != substr($v, 0, 1)) //判断是不是文件上传
                {
                    $postBodyString .= "$k=" . urlencode($v) . "&";
                    $encodeArray[$k] = $v;
                } else //文件上传用multipart/form-data，否则用www-form-urlencoded
                {
                    $postMultipart = true;
                    $encodeArray[$k] = new \CURLFile(substr($v, 1));
                }

            }
            unset ($k, $v);
            curl_setopt($ch, CURLOPT_POST, true);
            if ($postMultipart) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $encodeArray);
            } else {
                curl_setopt($ch, CURLOPT_POSTFIELDS, substr($postBodyString, 0, -1));
            }
        }

        if ($postMultipart) {

            $headers = array('content-type: multipart/form-data;charset=' . $this->postCharset . ';boundary=' . $this->_getMillisecond());
        } else {

            $headers = array('content-type: application/x-www-form-urlencoded;charset=' . $this->postCharset);
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $reponse = curl_exec($ch);

        if (curl_errno($ch)) {
            throw new \Exception('支付异常:'.curl_error($ch), 0);
        } else {
            $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if (200 !== $httpStatusCode) {
                throw new \Exception($reponse, $httpStatusCode);
            }
        }

        curl_close($ch);
        return $reponse;
    }
    protected function _getMillisecond() {
        list($s1, $s2) = explode(' ', microtime());
        return (float)sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
    }
}
